package pulpfizz.pulp;

import org.jbox2d.dynamics.Body;

import pulpcore.Input;
import pulpcore.Stage;
import pulpcore.animation.Easing;
import pulpcore.image.CoreGraphics;
import pulpcore.math.CoreMath;
import pulpcore.sprite.Group;
import pulpfizz.pulp.body.PhysicsLayer;

// TODO: Delete me and put functionality into Camera class.

/**
 * A Sprite group / layer that includes public methods for panning, zooming, and controlling
 * the movement of the layer in a camera-like way. Best used in conjunction with CameraControlledScene.
 * @author Greg
 *
 */
public class CameraSprite extends Group
{
	static final float MAX_ZOOM = 20.0f;
	Easing easing = Easing.REGULAR_OUT;
	
	/* Body following variables. */
	boolean followingBody = false;
	PhysicsLayer physics = null;
	Body followedBody = null;
	double followTolerance;

	/**
	 * Note that these are FIXED integers. See CoreMath and Sprite classes for more info.
	 */
	int anchorOffsetX = 0;
	int anchorOffsetY = 0;

	public CameraSprite()
	{
		super(0, 0, Stage.getWidth(), Stage.getHeight());
		setAnchor(Group.NORTH_WEST);
		refocus();
		pixelSnapping.set(false);
	}

	int zoomDuration = 1000;
	Easing ZOOM_EASE = Easing.STRONG_OUT;
	int moveDuration = zoomDuration;
	Easing MOVE_EASE = ZOOM_EASE;

	/**
	 * Zoom to a given zoom level. Zoom is defined as going from zero (totally zoomed out) to infinity (totally zoomed in).
	 * 
	 * A doubling of zoom level causes a halving of the amount of width and height that is shown.
	 */
	public void zoomTo(double newZ)
	{
		newZ = Math.min(newZ, MAX_ZOOM);
		scaleTo(getBaseWidth() * newZ, getBaseHeight() * newZ,
				zoomDuration, ZOOM_EASE);
	}

	double getBaseWidth()
	{
		return CoreMath.toDouble(getNaturalWidth());
	}
	
	double getBaseHeight()
	{
		return CoreMath.toDouble(getNaturalHeight());
	}
	
	/**
	 * Changes the anchor point of this sprite, making sure to offset its location to compensate for
	 * the anchor change.
	 */
	public void setAnchor(double newAnchorX, double newAnchorY)
	{
		double oldAnchorX = CoreMath.toDouble(getAnchorX());
		double oldAnchorY = CoreMath.toDouble(getAnchorY());
		double oldX = x.get();
		double oldY = y.get();
		this.anchorOffsetX = CoreMath.toFixed(newAnchorX);
		this.anchorOffsetY = CoreMath.toFixed(newAnchorY);
		double dX = (newAnchorX - oldAnchorX) * getZoom();
		double dY = (newAnchorY - oldAnchorY) * getZoom();
		x.set(oldX + dX);
		y.set(oldY + dY);
	}

	public void refocus()
	{
		double midX = Stage.getWidth() / 2.0;
		double midY = Stage.getHeight() / 2.0;
		double localFocusX = getLocalX(midX, midY);
		double localFocusY = getLocalY(midX, midY);
		setAnchor(localFocusX, localFocusY);
	}

	public void zoomBy(double factor)
	{

		double newZ = getZoom() * factor;
		zoomTo(newZ);
	}

	public void fastForward()
	{
		width.stopAnimation(true);
		height.stopAnimation(true);
		x.stopAnimation(true);
		y.stopAnimation(true);
	}
	
	public double getZoom()
	{
		return width.get() / getBaseWidth();
	}

	public void panTo(double targetX, double targetY)
	{
		moveTo(-targetX, targetY, moveDuration, MOVE_EASE);
	}

	public void panBy(double dX, double dY)
	{
		double newX = targetX() - dX;
		double newY = targetY() + dY;
		this.panTo(newX, newY);
	}

	double targetX()
	{
		return -x.get();
	}

	double targetY()
	{
		return y.get();
	}

	@Override
	protected int getAnchorX()
	{
		setAnchor(Group.NORTH_WEST);
		return super.getAnchorX() + anchorOffsetX;
	}

	@Override
	protected int getAnchorY()
	{
		setAnchor(Group.NORTH_WEST);
		return super.getAnchorY() + anchorOffsetY;
	}

	final double ZOOM_FACTOR = 1.5;
	
	boolean respondToInput = true;
	
	public void setRespondToInput(boolean respondToInput)
	{
		this.respondToInput = respondToInput;
	}
	
	@Override
	public void update(int elapsedTime)
	{
		super.update(elapsedTime);

		if (followingBody) followBody();
		
		if (!respondToInput)
		{
			return;
		}
		if (Input.isTyped(Input.KEY_Q))
		{
			refocus();
			zoomBy(ZOOM_FACTOR);
		}
		if (Input.isTyped(Input.KEY_W))
		{
			refocus();
			zoomBy(1 / ZOOM_FACTOR);
		}
		if (Input.isTyped(Input.KEY_LEFT))
		{
			double dX = .25 * Stage.getWidth();
			panBy(dX, 0);
		}
		if (Input.isTyped(Input.KEY_RIGHT))
		{
			double dX = -.25 * Stage.getWidth();
			panBy(dX, 0);
		}
		if (Input.isTyped(Input.KEY_UP))
		{
			double dY = .25 * Stage.getHeight();
			panBy(0, dY);
		}
		if (Input.isTyped(Input.KEY_DOWN))
		{
			double dY = -.25 * Stage.getHeight();
			panBy(0, dY);
		}

		int mouseWheel = Input.getMouseWheelRotation();
		if (mouseWheel != 0)
		{
			double mX = getLocalX(Input.getMouseX(),Input.getMouseY());
			double mY = getLocalY(Input.getMouseX(),Input.getMouseY());
			setAnchor(mX, mY);
			if (mouseWheel > 0)
				zoomBy(1 / ZOOM_FACTOR);
			else
				zoomBy(ZOOM_FACTOR);
		}
		
	}

	@Override
	protected void drawSprite(CoreGraphics g)
	{
		super.drawSprite(g);
	}

	public int getZoomDuration()
	{
		return zoomDuration;
	}

	public void setZoomDuration(int zoomDuration)
	{
		this.zoomDuration = zoomDuration;
	}

	public int getMoveDuration()
	{
		return moveDuration;
	}

	public void setMoveDuration(int moveDuration)
	{
		this.moveDuration = moveDuration;
	}
	
	/**
	 * Start tracking the camera on the given Body in the given
	 * PhysicsLayer, with a set fractional tolerance. A tolerance
	 * of 1.0 means that the camera will only move when the body is
	 * completely offscreen, and 0.0 means it will exactly track
	 * the body (up to a speed implied by the camera's easing
	 * speed, that is).
	 * @param phys
	 * @param b
	 * @param tolerance
	 */
	public void followBody(PhysicsLayer phys, Body b, double tolerance) {
		followedBody = b;
		followTolerance = tolerance;
		physics = phys;
		followingBody = true;
	}
	
	/**
	 * Stop following the currently tracked body.
	 * Does not reset the Body being tracked, so
	 * after stopping you may call resumeFollowingBody
	 * to go back to the previously tracked body.
	 */
	public void stopFollowingBody() {
		followingBody = false;
	}
	
	/**
	 * Resumes tracking the previous followed Body.
	 * Does nothing if no Body was previously followed.
	 */
	public void resumeFollowingBody() {
		if (followedBody == null ||
			physics == null) return;
		followingBody = true;
	}
	
	public boolean isFollowingBody() {
		return followingBody;
	}
	
	/**
	 * Set the follow tolerance for the tracked body -
	 * a tolerance of 1.0 means that the camera will
	 * only move once the body is completely offscreen,
	 * and 0.0 means that the body will be tracked to
	 * the screen center always.
	 * @param tolerance
	 */
	public void setFollowTolerance(double tolerance) {
		followTolerance = tolerance;
	}
	
	private void followBody() {
		double bsx = physics.getViewX(physics.getCanvasCMX(followedBody),physics.getCanvasCMY(followedBody));
        double bsy = physics.getViewY(physics.getCanvasCMX(followedBody),physics.getCanvasCMY(followedBody));
        double xPan = -(bsx-.5*Stage.getWidth());
        double yPan = -(bsy-.5*Stage.getHeight());
        if (Math.abs(xPan) < .5*Stage.getWidth()*followTolerance) xPan = 0;
        else xPan -= ((xPan>0)?1.0:-1.0)*.5*(Stage.getWidth() * followTolerance);
        if (Math.abs(yPan) < .5*Stage.getHeight()*followTolerance) yPan = 0;
        else yPan -= ((yPan>0)?1.0:-1.0)*.5*(Stage.getHeight() * followTolerance);
        panBy(xPan, yPan);
	}
}
